package utils

import (
    "bytes"
    "encoding/json"
    "fmt"
    "image"
    "image/gif"
    "image/jpeg"
    "image/png"
    "io/ioutil"
    "net/http"
    "os"
    "os/exec"
    "path/filepath"
    "regexp"
    "runtime"
    "strconv"
    "strings"
    "time"

    "golang.org/x/image/bmp"
)

func WindowsSystemDrive() string {
    if vol, ok := os.LookupEnv("SystemDrive"); ok {
        return vol
    }
    if vol, ok := os.LookupEnv("SystemRoot"); ok {
        return filepath.VolumeName(vol)
    }
    if vol, ok := os.LookupEnv("WinDir"); ok {
        return filepath.VolumeName(vol)
    }
    return "C:"
}

func LoadImageFromFile(fname string) (image.Image, error) {
    bin, err := LoadBinaryFromFile(fname)
    if err != nil {
        return nil, err
    }
    typ := http.DetectContentType(bin)
    switch typ {
    case "image/jpeg":
        return jpeg.Decode(bytes.NewReader(bin))
    case "image/png":
        return png.Decode(bytes.NewReader(bin))
    case "image/bmp":
        return bmp.Decode(bytes.NewReader(bin))
    case "image/gif":
        return gif.Decode(bytes.NewReader(bin))
    default:
        return nil, fmt.Errorf("unknown image format %s", typ)
    }
}

func LoadBinaryFromFile(fname string) ([]byte, error) {
    var fname_full string
    if filepath.IsAbs(fname) {
        fname_full = fname
    } else {
        fname_full = filepath.Join(ProjectRoot(), fname)
    }

    f, err := ioutil.ReadFile(fname_full)
    if err != nil {
        return nil, err
    }
    return f, nil
}

func LoadJSONFromFile(fname string, obj interface{}) error {
    var fname_full string
    if filepath.IsAbs(fname) {
        fname_full = fname
    } else {
        fname_full = filepath.Join(ProjectRoot(), fname)
    }

    f, err := ioutil.ReadFile(fname_full)
    if err != nil {
        return err
    }

    return json.Unmarshal(f, obj)
}

func ProjectRoot() string {
    exeFullPath, err := os.Executable()
    if err != nil {
        panic(err)
    }
    exePath := filepath.Dir(exeFullPath)
    return exePath
}

func IsFileExists(filename string) bool {
    _, err := os.Lstat(filename)
    return err == nil
}


func SearchUpFile(base string, filename string) (fullpath string, err error) {
    si, err := os.Stat(base)
    if err != nil {
        return
    }
    if !si.IsDir() {
        err = fmt.Errorf("%s is not a directory", base)
        return
    }
    curr := base
    for {

        fullpath = filepath.Join(curr, filename)
        si, err = os.Stat(fullpath)
        if err == nil && !si.IsDir() {
            return
        }
        curr_next := filepath.Dir(curr)
        if curr_next == curr {
            err = fmt.Errorf("not found")
            return
        }
        curr = curr_next
    }
}


func UserHome() (string, error) {
    return "", nil
}


func InterfaceToInt(v interface{}) (out int, err error) {
    switch v.(type) {
    case int:
        out = v.(int)
        return
    case int8:
        out = int(v.(int8))
        return
    case int16:
        out = int(v.(int16))
        return
    case int32:
        out = int(v.(int32))
        return
    case int64:
        out = int(v.(int64))
        return
    case uint:
        out = int(v.(uint))
        return
    case uint8:
        out = int(v.(uint8))
        return
    case uint16:
        out = int(v.(uint16))
        return
    case uint32:
        out = int(v.(uint32))
        return
    case uint64:
        out = int(v.(uint64))
        return
    case string:
        return strconv.Atoi(v.(string))
    default:
        err = fmt.Errorf("invalid type")
        return
    }
}


func InterfaceToString(v interface{}) (out string, err error) {
    switch v.(type) {
    case string:
        return v.(string), nil
    case int, int8, int16, int32, int64:
        return fmt.Sprint(v), nil
    case uint, uint8, uint16, uint32, uint64:
        return fmt.Sprint(v), nil
    case float32, float64:
        return fmt.Sprint(v), nil
    case []byte:
        return string(v.([]byte)), nil
    default:
        return "", fmt.Errorf("not supported type")
    }
}


func MakeFullDir(p string) error {
    return os.MkdirAll(p, 0666)
}

func SetSystemDate(newTime time.Time) error {
    if _, err := exec.LookPath("date"); err != nil {
        return fmt.Errorf("cannot found date command")
    }
    dateString := newTime.Format("20060102 15:04:05")

    if err := exec.Command("sudo", "date", "--set", dateString).Run(); err != nil {
        return err
    }







    return nil
}

func Reboot() error {
    if _, err := exec.LookPath("reboot"); err != nil {
        return fmt.Errorf("cannot found reboot command")
    }
    return exec.Command("sudo", "reboot").Run()
}

func GoID() int {
    var buf [64]byte
    n := runtime.Stack(buf[:], false)
    idField := strings.Fields(strings.TrimPrefix(string(buf[:n]), "goroutine "))[0]
    id, err := strconv.Atoi(idField)
    if err != nil {
        panic(fmt.Sprintf("cannot get goroutine id: %v", err))
    }
    return id
}



func ForceExit() {
    proc, err := os.FindProcess(os.Getpid())
    if err == nil {
        _ = proc.Kill()
    }
}


var re_unix = regexp.MustCompile(`[<>:"/\\|?*\x{0000}-\x{001F}]`)
var re_winname = regexp.MustCompile(`^(con|prn|aux|nul|com\d|lpt\d)$`)
var re_misc = regexp.MustCompile(`^\.\.?$`)

func ValidFileName(fname string) bool {
    if len(fname) > 255 {
        return false
    }
    if re_unix.MatchString(fname) {
        return false
    }
    if re_winname.MatchString(fname) {
        return false
    }
    if re_misc.MatchString(fname) {
        return false
    }
    return true
}
